/* Copyright (c) 2009 Christopher Humphries, Maxime Simon
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package userinterface;

import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.ArrayList;
import java.util.List;

import javax.swing.AbstractAction;
import javax.swing.KeyStroke;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;

import command.Command;

import data.Buffer;

/**
 * @author Humphries Christopher - Simon Maxime
 * 
 */
public class ActionHandler implements InterfaceActionHandler, KeyListener {

    private GraphicalInterface m_interface;
    private Buffer m_buffer;

    private String m_currentFileURL;
    private char m_latestTypedCharacter;
    private int m_tempStart, m_tempEnd;

    private int m_modifierKey;
    private boolean m_modifierDown;

    private macro.Originator m_macroOriginator;
    private macro.Caretaker m_macroCaretaker;

    private Command m_copy, m_cut, m_erase, m_paste, m_insert;
    private Command m_newFile, m_openFile, m_saveFile;
    private Command m_discardMacro, m_acceptMacro, m_deleteMacro, m_runMacro;

    private AbstractAction a_delete, a_backspace, a_copy, a_cut, a_paste;
    private AbstractAction a_about, a_newFile, a_openFile, a_saveFile;
    private AbstractAction a_acceptMacro, a_addMacro, a_deleteMacro,
	    a_discardMacro, a_runMacro, a_toggleMacroPanel;

    private TreeSelectionListener l_macroTreeSelection;

    private int m_currentMacroIndex;
    private List<DefaultMutableTreeNode> m_macroNodeList;
    private boolean m_macroRecording;
    private int m_selectedMacroIndex;

    /**
     * 
     */
    public void createCommands() {
	a_delete = new userinterface.action.Delete(m_interface, this,
		m_interface.m_textArea, m_erase);
	a_backspace = new userinterface.action.Backspace(m_interface, this,
		m_interface.m_textArea, m_erase);
	a_copy = new userinterface.action.Copy(m_interface, this, m_copy);
	a_cut = new userinterface.action.Cut(m_interface, this,
		m_interface.m_textArea, m_cut);
	a_paste = new userinterface.action.Paste(m_interface, this,
		m_interface.m_textArea, m_buffer, m_paste);

	m_interface.m_textArea.getInputMap().put(
		KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), "delete");
	m_interface.m_textArea.getInputMap().put(
		KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0), "backspace");
	m_interface.m_textArea.getInputMap().put(
		KeyStroke.getKeyStroke(KeyEvent.VK_COPY, 0), "copy");
	m_interface.m_textArea.getInputMap().put(
		KeyStroke.getKeyStroke(KeyEvent.VK_CUT, 0), "cut");
	m_interface.m_textArea.getInputMap().put(
		KeyStroke.getKeyStroke(KeyEvent.VK_PASTE, 0), "paste");

	m_interface.m_textArea.getActionMap().put("delete", a_delete);
	m_interface.m_textArea.getActionMap().put("backspace", a_backspace);
	m_interface.m_textArea.getActionMap().put("copy", a_copy);
	m_interface.m_textArea.getActionMap().put("cut", a_cut);
	m_interface.m_textArea.getActionMap().put("paste", a_paste);
    }

    /**
     * 
     */
    public void createButtonActions() {
	setMacroRecording(false);
	setCurrentMacroIndex(0);
	m_macroNodeList = new ArrayList<DefaultMutableTreeNode>();

	a_about = new userinterface.action.About(m_interface);
	a_newFile = new userinterface.action.NewFile(m_interface, m_newFile);
	a_openFile = new userinterface.action.OpenFile(
		m_interface.m_fileChooser, m_buffer, m_interface,
		m_interface.m_textArea, this, m_openFile);
	a_saveFile = new userinterface.action.SaveFile(
		m_interface.m_fileChooser, m_interface, this, m_saveFile);

	a_acceptMacro = new userinterface.action.AcceptMacro(
		m_interface.m_macroTree, m_interface, this,
		m_interface.m_macroButtonAdd, m_interface.m_macroButtonAccept,
		m_interface.m_macroButtonDiscard, m_interface.m_macroButtonRun,
		m_interface.m_macroButtonDelete, m_acceptMacro);
	a_addMacro = new userinterface.action.AddMacro(m_interface.m_macroTree,
		m_macroNodeList, m_interface.m_macroTreeRoot, this,
		m_interface.m_macroButtonAdd, m_interface.m_macroButtonAccept,
		m_interface.m_macroButtonDiscard, m_interface.m_macroButtonRun,
		m_interface.m_macroButtonDelete);
	a_deleteMacro = new userinterface.action.DeleteMacro(
		m_interface.m_macroTree, m_interface,
		m_interface.m_macroTreeRoot, this,
		m_interface.m_macroButtonAdd, m_interface.m_macroButtonAccept,
		m_interface.m_macroButtonDelete, m_interface.m_macroButtonRun,
		m_deleteMacro);
	a_discardMacro = new userinterface.action.DiscardMacro(
		m_interface.m_macroTree, m_interface, m_macroNodeList, this,
		m_interface.m_macroButtonAdd, m_interface.m_macroButtonAccept,
		m_interface.m_macroButtonRun, m_interface.m_macroButtonDiscard,
		m_discardMacro);
	a_runMacro = new userinterface.action.RunMacro(m_interface.m_macroTree,
		m_interface, m_interface.m_macroTreeRoot, this,
		m_interface.m_macroButtonAdd, m_interface.m_macroButtonAccept,
		m_interface.m_macroButtonDiscard, m_interface.m_macroButtonRun,
		m_interface.m_macroButtonDelete, m_runMacro);
	a_toggleMacroPanel = new userinterface.action.ToggleMacroPanel(
		m_interface.m_splitPane, m_interface.m_macroScrollPane);

	m_interface.m_buttonInfo.setAction(a_about);
	m_interface.m_buttonNew.setAction(a_newFile);
	m_interface.m_buttonOpen.setAction(a_openFile);
	m_interface.m_buttonSave.setAction(a_saveFile);

	m_interface.m_macroButtonAccept.setAction(a_acceptMacro);
	m_interface.m_macroButtonAdd.setAction(a_addMacro);
	m_interface.m_macroButtonDelete.setAction(a_deleteMacro);
	m_interface.m_macroButtonDiscard.setAction(a_discardMacro);
	m_interface.m_macroButtonRun.setAction(a_runMacro);
	m_interface.m_macroButtonToggle.setAction(a_toggleMacroPanel);
    }

    /**
     * 
     */
    public void createListeners() {
	l_macroTreeSelection = new TreeSelectionListener() {

	    @Override
	    public void valueChanged(TreeSelectionEvent e) {
		if (!macroRecording()) {
		    DefaultMutableTreeNode node = (DefaultMutableTreeNode) m_interface.m_macroTree.getLastSelectedPathComponent();
		    if (node == null) {
			return;
		    }
		    if (!node.isLeaf()) {
			m_interface.m_macroButtonRun.setVisible(true);
			m_interface.m_macroButtonDelete.setVisible(true);
		    } else {
			m_interface.m_macroButtonRun.setVisible(false);
			m_interface.m_macroButtonDelete.setVisible(false);
		    }
		}
	    }
	};

	m_interface.m_macroTree.getSelectionModel().addTreeSelectionListener(
		l_macroTreeSelection);
    }

    /**
     * 
     */
    public void handleInsertCommand() {
	try {
	    m_insert.execute();
	    m_interface.updateHistBar();

	    if (selectionStart() == selectionEnd()) {
		addMacroAction("Insert '" + m_latestTypedCharacter + "' at "
			+ selectionStart());
	    } else {
		addMacroAction("Replace from " + selectionStart() + ","
			+ selectionEnd() + " with '" + m_latestTypedCharacter
			+ "'");
	    }
	} catch (Exception e) {
	    m_interface.showErrorMsg("Failed to insert the text.");
	}
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.awt.event.KeyListener#keyPressed(java.awt.event.KeyEvent)
     */
    public void keyPressed(KeyEvent e) {
	m_modifierDown = (e.getModifiersEx() == m_modifierKey);
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.awt.event.KeyListener#keyReleased(java.awt.event.KeyEvent)
     */
    public void keyReleased(KeyEvent e) {
	m_modifierDown = (e.getModifiersEx() == m_modifierKey);
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.awt.event.KeyListener#keyTyped(java.awt.event.KeyEvent)
     */
    public void keyTyped(KeyEvent e) {
	m_latestTypedCharacter = e.getKeyChar();

	if (!m_modifierDown && m_latestTypedCharacter != '\b'
		&& m_latestTypedCharacter != '\u007F') {
	    handleInsertCommand();
	}
    }

    /**
     * @param label
     */
    public void addMacroAction(String label) {
	if (macroRecording()) {
	    m_macroNodeList.get(m_macroNodeList.size() - 1).add(
		    new DefaultMutableTreeNode(label));
	    m_macroNodeList.get(m_macroNodeList.size() - 1).setUserObject(
		    "Macro "
			    + (currentMacroIndex() + 1)
			    + " ("
			    + m_macroNodeList.get(m_macroNodeList.size() - 1).getChildCount()
			    + ")");
	    m_interface.m_macroTree.updateUI();
	}
    }

    /*
     * (non-Javadoc)
     * 
     * @see userinterface.InterfaceActionHandler#latestTypedCharacter()
     */
    public char latestTypedCharacter() {
	return m_latestTypedCharacter;
    }

    /**
     * @return
     */
    public int selectionStart() {
	return m_interface.m_textArea.getSelectionStart();
    }

    /**
     * @return
     */
    public int selectionEnd() {
	return m_interface.m_textArea.getSelectionEnd();
    }

    /**
     * @param m_macroRecording
     */
    public void setMacroRecording(boolean m_macroRecording) {
	this.m_macroRecording = m_macroRecording;
    }

    /**
     * @return
     */
    public boolean macroRecording() {
	return m_macroRecording;
    }

    /**
     * @param currentMacroIndex
     */
    public void setCurrentMacroIndex(int currentMacroIndex) {
	this.m_currentMacroIndex = currentMacroIndex;
    }

    /**
     * @return
     */
    public int currentMacroIndex() {
	return m_currentMacroIndex;
    }

    /**
     * @param tempStart
     */
    public void setTempStart(int tempStart) {
	this.m_tempStart = tempStart;
    }

    /**
     * @return
     */
    public int tempStart() {
	return m_tempStart;
    }

    /**
     * @param tempEnd
     */
    public void setTempEnd(int tempEnd) {
	this.m_tempEnd = tempEnd;
    }

    /**
     * @return
     */
    public int tempEnd() {
	return m_tempEnd;
    }

    /**
     * @param gInterface
     * @param buffer
     * @param historyOriginator
     * @param historyCaretaker
     * @param macroOriginator
     * @param macroCaretaker
     */
    public ActionHandler(GraphicalInterface gInterface, Buffer buffer,
	    history.Originator historyOriginator,
	    history.Caretaker historyCaretaker,
	    macro.Originator macroOriginator, macro.Caretaker macroCaretaker) {
	m_buffer = buffer;
	m_interface = gInterface;
	m_macroOriginator = macroOriginator;
	m_macroCaretaker = macroCaretaker;
	m_currentFileURL = "";

	m_copy = new command.Copy(m_buffer, this, m_macroOriginator);
	m_cut = new command.Cut(m_buffer, this, m_macroOriginator);
	m_erase = new command.Erase(m_buffer, this, m_macroOriginator);
	m_paste = new command.Paste(m_buffer, this, m_macroOriginator);
	m_insert = new command.Insert(m_buffer, this, m_macroOriginator);
	m_newFile = new command.NewFile(m_buffer);
	m_openFile = new command.OpenFile(m_buffer, this);
	m_saveFile = new command.SaveFile(m_buffer, this);
	m_acceptMacro = new command.acceptMacro(this, m_macroOriginator,
		m_macroCaretaker);
	m_deleteMacro = new command.deleteMacro(this, m_macroOriginator,
		m_macroCaretaker);
	m_discardMacro = new command.discardMacro(this, m_macroOriginator,
		m_macroCaretaker);
	m_runMacro = new command.runMacro(this, m_buffer, m_macroOriginator,
		m_macroCaretaker);

	if (System.getProperty("os.name").equals("Mac OS X")) {
	    m_modifierKey = java.awt.event.InputEvent.META_DOWN_MASK;
	} else {
	    m_modifierKey = java.awt.event.InputEvent.CTRL_DOWN_MASK;
	}

	m_interface.m_textArea.addKeyListener(this);
    }

    /**
     * @param currentFileUrl
     */
    public void setCurrentFileURL(String currentFileUrl) {
	this.m_currentFileURL = currentFileUrl;
    }

    /**
     * @return
     */
    public String currentFileURL() {
	return m_currentFileURL;
    }

    /**
     * @return
     */
    public int selectedMacroIndex() {
	return m_selectedMacroIndex;
    }

    /**
     * @param selectedMacroIndex
     */
    public void setSelectedMacroIndex(int selectedMacroIndex) {
	m_selectedMacroIndex = selectedMacroIndex;
    }
}
